/**
 * 
 */
package com.mobilottery.server;

import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.persistence.EntityManager;

import org.apache.log4j.Logger;

import com.mobilottery.Constants;
import com.mobilottery.external.ILotteryHandler;
import com.mobilottery.external.ILotterySystem;
import com.mobilottery.simulation.LotterySystemSimulator;

/**
 * @author ziv, fedor
 * Singleton class responsible for communicating with the lottery game providers and the mobilelottery system.
 * Holds game manager instance to provide game management funcitonality. 
 * On startup init() must be called to initialize external lottery system according to the property @see ConfManager.PROPERTY_LOTTERY_SYSTEM_IMPL from the configuration file.
 * On shutdown destroy() must be called to clean up the system properly. 
 */
public class GameProvider implements ILotteryHandler 
{

	private static Logger logger = Logger.getLogger(GameProvider.class);
    private static GameProvider instance = new GameProvider(); //singleton instance
    
    GameManager gameManager = new GameManager();
    private ILotterySystem lotterySystemInstance = createLotterySystemInstance();

    private GameProvider()
    {    
    }

    /**
     * Callback implementation for new game registration
     * Called by external system to register a new available game
     */
    public String addNewAvailableGame(Long gameId, String description, float participationCost, float maxPrize, 
            Date openDate, Date closeDate, Date resultDate) 
    {
    	try
    	{
	        Game g = new Game(gameId, description, participationCost, maxPrize, openDate, closeDate, resultDate);
	        gameManager.createNewGame(g);
	        return Constants.RESULT_OK;
    	}
    	catch(Exception ex)
    	{
    		logger.error("Error creating new game", ex);
    		return "Error creating new game: " + ex.getMessage();
    	}
    }

    /**
     * Add user details to game participation table
     * @return transaction id number or exception in case of error
     * @throws InvalidArgumentException 
     * @throws RegistrationErrorException 
     */
    public Long addGameParticipation(GameParticipation gameParticipation) throws InvalidArgumentException, RegistrationErrorException 
    {
    	if(gameParticipation.getGuessedNumbers().size()!=6)
    	{
    		throw new InvalidArgumentException("participation requires 6 different numbers (" + gameParticipation.getGuessedNumbers().size() + " received)");
    	}
    	if(gameParticipation.getGame().getWinningNumbersRaw()!=null || gameParticipation.getGame().getCloseDate().getTime()<new Date().getTime() )
    	{
    		throw new InvalidArgumentException("game was already closed for participation");
    	}

    	String transaction_id = lotterySystemInstance.participateInGame(gameParticipation.getGame().getId(), gameParticipation.getUser().getId(), gameParticipation.getUser().getCreditCardNumber(),gameParticipation.getGuessedNumbers()); 
    	
    	if (transaction_id.startsWith("ERROR")) //only if we haver received a valid transaction id we will presist the data
    	{
    		throw new RegistrationErrorException(transaction_id); 
    	}
    	else
    	{
    			//set transaction_id in the class and presist it to the DB
    			gameParticipation.setTransactionId(Long.decode(transaction_id));
    			//persist 
    			PersistenceManager.getInstance().persist(gameParticipation);
    	}    	
    	return Long.decode(transaction_id);
    }


    /**
     * Retrieves game provider implementation singleton instance
     * @return
     */
    public static GameProvider getInstance() {
        return instance;
    }
    
    /**
     * Retrieves proxy to external lottery system instance
     * @return
     */
    public ILotterySystem getLotterySystem() {
        return lotterySystemInstance;
    }
    
    /*
     * Creates the implementation of external lottery system access
     * @return
     */
    private ILotterySystem createLotterySystemInstance()
    {    	
    	String lotterySystemImpl = "undefined"; 
    	try
    	{
	    	lotterySystemImpl = 
	    		ConfManager.getInstance().getProperty(ConfManager.PROPERTY_LOTTERY_SYSTEM_IMPL);
	    	ILotterySystem result = (ILotterySystem)Class.forName(lotterySystemImpl).newInstance();
	    	return result;
    	}
    	catch(Exception ex)
    	{
    		//we don't expect any exception here, if one is thrown the configuration is fatally wrong.  
    		//so we don't expose checked exception but just create runtime exception
    		throw new RuntimeException("error instantiating lottery system implementaiton '" + lotterySystemImpl + "', the check lottery.properties configuration for valid " + ConfManager.PROPERTY_LOTTERY_SYSTEM_IMPL + " property ", ex);
    	}
    }
    
    /**
     * Called by system to initialize the game provider
     */
    public void init()
    {
		logger.info("initializing game provider");
		ILotterySystem lottterySystem = getLotterySystem();
		lottterySystem.setLotteryHandler(this);
		if(!UserManager.getInstance().hasAdminUsers())
		{
			logger.info("no administrator users were defined in the system, creating default account admin/admin");
			Admin defaultAdmin = new Admin();
			defaultAdmin.setEmail("admin@mobilottery.com");
			defaultAdmin.setFirstName("Default");
			defaultAdmin.setUserName("admin");
			defaultAdmin.setPasswordText("admin");
			UserManager.getInstance().registerUser(defaultAdmin);
		}
		
    }

    public void destroy()
    {
		logger.info("closing game provider");
		ILotterySystem lottterySystem = getLotterySystem();
		if(lottterySystem instanceof LotterySystemSimulator)
		{
			((LotterySystemSimulator)lottterySystem).destroy();
		}
    }    
    
	public GameManager getGameManager() {
		return gameManager;
	}

    /**
     * Callback implementation for game update
     * Called by external system to update game status
     */
	public String updateGameResults(Long gameId, List<Long> winingTransactions, List<Float> transacitonWiningAmounts, Set<Long> winningNumber) 
	{
		logger.debug("updating game results for game " + gameId);
		try
		{
			//get session
			EntityManager manager =  PersistenceManager.getInstance().getEntityManager();
			try
			{
				//start transaction
				manager.getTransaction().begin();
				
				//update game
				Game game = gameManager.getGameByExternalId(gameId);
				game.setWiningNumbers(winningNumber);
				manager.persist(game);
				
				//update game instances
				
				//get all participations for the game
				//(not production scalable but appropriate for small colledge project) 
				List<GameParticipation> participations = game.getGameParticipations();
				logger.debug("updating results for  " + participations.size() + " participations");
				if(!participations.isEmpty())
				{
				    Map<Long, Float> transactionToWinAmount = new HashMap<Long, Float>(winingTransactions.size());
				    for(int i=0; i< winingTransactions.size(); i++)
				    {
				    	transactionToWinAmount.put(winingTransactions.get(i), transacitonWiningAmounts.get(i));
				    }
				    
				    for(GameParticipation participation : participations)
				    {
				    	Float amountWon = transactionToWinAmount.get(participation.getTransactionId());
				    	if(amountWon==null) amountWon = 0f;
				    	participation.setWinningAmount(amountWon);
				    	manager.persist(participation);
				    	
				    }				    
				}
			    //commit transaction
				manager.getTransaction().commit();
			}
			catch(Exception e)
			{
				try
				{
					//try rollback
					manager.getTransaction().setRollbackOnly();
				}
				catch(Exception eee){}
				throw e;
			}
			finally
			{
				//close session
				PersistenceManager.closeInstance();
			}
		}
		catch(Exception ex)
		{
			logger.error("error updating game results", ex);
			return "Error: " + ex.getMessage();
			
		}
		return null;
	}
	
	/*public static void main(String args[])
	{
		 Game game =  GameProvider.getInstance().getGameManager().getGame(1252L);
		 List<GameParticipation> gameParticipations = game.getGameParticipations();
		 System.out.println(gameParticipations.size());
		
	}*/

}
